%{
  /* Aqui, pode-se inserir qualquer codigo C necessario ah compilacao
   * final do parser. Sera copiado tal como esta no inicio do y.tab.c
   * gerado por Yacc.
   */
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include "node.h"
  #include "data_structs/symbol_table.h"

#define BUFFER_SIZE 1024

  extern int offset;

  char lexeme_buffer[BUFFER_SIZE];
  int line_number = 1;
  symbol_t symbol_table;

  void inherit_attributes(Node* father)
  {
    if (father == NULL) return;

    Node *son = father;

    if (son->type == true_node)
        append_inst_tac(&son->att->code, create_inst_tac("goto",son->att->t, "", ""));

    else if (son->type == false_node)
        append_inst_tac(&son->att->code, create_inst_tac("goto",son->att->f, "", ""));

    else if (son->type == if_node) {

        Node *B = child(son, 0);
        Node *S1 = child(son,1);
        Node *S2 = child(son,2);

        B->att->t = create_label();
        B->att->f = father->att->next;

        if (S2->type == else_node)
            B->att->f = create_label();

        // WE MUST GO DEEPEER
        inherit_attributes(B);
        inherit_attributes(S1);
        inherit_attributes(S2);

        S1->att->next = father->att->next;

        cat_tac(&father->att->code, &B->att->code);
        append_inst_tac(&father->att->code, create_inst_tac(B->att->t,":","","") );
        cat_tac(&father->att->code, &S1->att->code);

        if (S2->type == else_node)
        {
            append_inst_tac(&father->att->code, create_inst_tac("goto",father->att->next,"","") );
            append_inst_tac(&father->att->code, create_inst_tac(B->att->f,":","","") );
            cat_tac(&father->att->code, &S2->att->code);
        }

        append_inst_tac(&father->att->code, create_inst_tac(father->att->next,":","","") );
    }

    else if (son->type == else_node) {
        Node *S1 = child(son,0);

        S1->att->next = father->att->next;

        inherit_attributes(S1);

        cat_tac(&father->att->code, &S1->att->code);
    }

    else if (son->type == while_node) {

        Node *B = child(son, 0);
        Node *S1 = child(son,1);

        B->att->f = father->att->next;
        B->att->t = create_label();

        inherit_attributes(B);
        inherit_attributes(S1);

        father->att->begin = create_label();
        S1->att->next = father->att->begin;

        append_inst_tac(&father->att->code, create_inst_tac(father->att->begin,":","",""));
        cat_tac(&father->att->code, &B->att->code);
        append_inst_tac(&father->att->code, create_inst_tac(B->att->t,":","",""));
        cat_tac(&father->att->code, &S1->att->code);
        append_inst_tac(&father->att->code, create_inst_tac("goto",father->att->begin,"",""));
        append_inst_tac(&father->att->code, create_inst_tac(father->att->next,":","",""));
    }

    else if (son->type == or_node) {
        Node *B1 = child(son,0);
        Node *B2 = child(son,1);

        B1->att->t = father->att->t;
        B1->att->f = create_label();
        B2->att->t = father->att->t;
        B2->att->f = father->att->f;

        inherit_attributes(B1);
        inherit_attributes(B2);

        cat_tac(&father->att->code, &B1->att->code);
        append_inst_tac(&father->att->code, create_inst_tac(B1->att->f,":","",""));
        cat_tac(&father->att->code, &B2->att->code);
    }

    else if (son->type == and_node) {
        Node *B1 = child(son,0);
        Node *B2 = child(son,1);

        B1->att->t = create_label();
        B1->att->f = father->att->f;
        B2->att->t = father->att->t;
        B2->att->f = father->att->f;

        inherit_attributes(B1);
        inherit_attributes(B2);

        cat_tac(&father->att->code, &B1->att->code);
        append_inst_tac(&father->att->code, create_inst_tac(B1->att->t,":","",""));
        cat_tac(&father->att->code, &B2->att->code);
    }

    else if (son->type == not_node) {
        Node *B1 = child(son,0);
        B1->att->t = father->att->f;
        B1->att->f = father->att->t;

        inherit_attributes(B1);

        cat_tac(&father->att->code, &B1->att->code);
    }

    else if (son->type == op_node) {
        Node *E1 = child(son,0);
        Node *E2 = child(son,1);

        cat_tac(&father->att->code, &E1->att->code);
        cat_tac(&father->att->code, &E2->att->code);

        struct tac *tmp = create_inst_tac(E1->att->local,father->lexeme, E2->att->local, father->att->t);
        tmp->type = 1;

        append_inst_tac(&father->att->code,tmp);

        append_inst_tac(&father->att->code,create_inst_tac("goto", father->att->f,"",""));
    }

	 else {
		int i;

	 	for(i = 0; i < son->nb_children; i++) {
			inherit_attributes(child(son,i));
		}
	 }

  }

%}

%union {
  char* cadeia;
  struct _node * no;
  int tipo;
}


%token<cadeia> IDF
%token<cadeia> INT
%token<cadeia> DOUBLE
%token<cadeia> REAL
%token<cadeia> CHAR
%token QUOTE
%token DQUOTE
%token LE
%token GE
%token EQ
%token NE
%token AND
%token OR
%token NOT
%token IF
%token THEN
%token ELSE
%token WHILE
%token FOR
%token NEXT
%token REPEAT
%token UNTIL
%token CASE
%token CONST
%token<cadeia> INT_LIT
%token<cadeia> F_LIT
%token END
%token TRUE
%token FALSE

%type<no> code
%type<no> acoes
%type<no> comando
%type<no> enunciado
%type<no> expr
%type<no> lvalue
%type<no> listaexpr
%type<no> chamaproc
%type<no> tipo
%type<no> tipounico
%type<no> tipolista
%type<no> listadupla
%type<no> declaracoes
%type<no> declaracao
%type<no> listadeclaracao
%type<no> expbool
%type<no> fiminstcontrole

%start code

%%
code: declaracoes acoes
        {
            syntax_tree = create_node(0, program_node, "Code", $2->att, 2, $1, $2);
        }
    | acoes 
		  { 
				syntax_tree = $1;
				//processar atributos herdados aqui
      	   inherit_attributes($$);
		  }
    ;

declaracoes: declaracao ';'              { $$ = $1; }
           | declaracoes declaracao ';'
           {
                $$ = create_node(0, decl_node, "Declaracoes", NULL, 2, $1, $2);
           }
           ;

declaracao: listadeclaracao ':' tipo
           {
                Node_type type = $3->type;
                int size = get_node_size(type);
                Node* next = $1;

                while (next != NULL)
                {
                    entry_t *e = malloc(sizeof(entry_t));
                    e->name = next->lexeme;
                    //printf("%s\n",e->name);
                    e->type = type;
                    e->size = size;
                    symbol_table.offset += e->size;
                    e->desloc = symbol_table.offset;
                    insert(&symbol_table,e);
                    next = child(next, 0);
                }

                $$ = create_node(0, decl_node, "Lista Declaracao",  NULL, 2, $1, $3 );
           }

listadeclaracao: IDF
                {
                    $$ = create_leaf(0, decl_list_node, $1, create_attr() );
                }
               | IDF ',' listadeclaracao
                {
                    $$ = create_node(0, $3->type, $1, $3->att, 1, $3);
                }
               ;

tipo: tipounico { $$ = $1; }
    | tipolista { $$ = $1; }
    ;

tipounico: INT
            { $$ = create_leaf(0, int_node,    $1, create_attr() ); }
         | DOUBLE
            { $$ = create_leaf(0, double_node, $1, create_attr() ); }
         | REAL
            { $$ = create_leaf(0, float_node,  $1, create_attr() ); }
         | CHAR
            { $$ = create_leaf(0, char_node,   $1, create_attr() ); }
         ;

tipolista: INT '[' listadupla ']'
                { $$ = create_node(0, int_node,    $1, create_attr(), 1, $3); }
         | DOUBLE '[' listadupla ']'
                { $$ = create_node(0, double_node, $1, create_attr(), 1, $3); }
         | REAL '[' listadupla ']'
                { $$ = create_node(0, float_node,  $1, create_attr(), 1, $3); }
         | CHAR '[' listadupla ']'
                { $$ = create_node(0, char_node,   $1, create_attr(), 1, $3); }
         ;

listadupla: INT_LIT ':' INT_LIT
                {
                    sprintf(lexeme_buffer,"[%d:%d]",atoi($1),atoi($3));
                    $$ = create_leaf(0, range_node, lexeme_buffer, NULL);
                }

          | INT_LIT ':' INT_LIT ',' listadupla
                {
                    sprintf(lexeme_buffer,"[%d:%d]",atoi($1),atoi($3));
                    $$ = create_node(0, char_node, lexeme_buffer, NULL,1,$5);
                }
          ;

acoes: comando ';'          { $$ = $1; }
    | comando ';' acoes
                    {
                        sprintf(lexeme_buffer,"%s ; %s", $1->lexeme, $3->lexeme);
                        cat_tac(&$1->att->code, &$3->att->code);
                        $$ = create_node(0, $1->type, lexeme_buffer, $1->att, 2, $1,$3);
                    }
    ;

comando: lvalue '=' expr
            {
                $1->att->code = create_tac();
                $1->att->code->inst = create_inst_tac($1->att->local,$3->att->local,"","");

                cat_tac(&$3->att->code, &$1->att->code);
                $$ = create_node(0, assign_node, expr_lexeme($1,$3,":="), $3->att, 2, $1,$3);
            }
       | enunciado
            {
                $$ = $1;

                $$->att->next = create_label();
            }
       ;

lvalue: IDF
      {
        Attributes* att = create_attr();
		  entry_t * entry = lookup(symbol_table,$1);
		  char tmp[8];
        if (entry == NULL)
        {
            printf("Use of undeclared variable '%s' at line %d.\n",$1,line_number);
        }
		  sprintf(tmp, "%03d(SP)", entry->desloc - entry->size);
        att->local = strdup(tmp);
        $$ = create_leaf(0, idf_node, $1, att);
      }
      | IDF '[' listaexpr ']'
            {
                sprintf(lexeme_buffer,"%s[]",$1);
                Attributes* att = create_attr();
                att->local = strdup(lexeme_buffer);
                $$ = create_node(0, idf_node, lexeme_buffer, att, 1, $3);
            }
      ;


listaexpr: expr { $$ = $1; }
       | expr ',' listaexpr  { $$ = create_node(0, $1->type, $1->lexeme , NULL, 1, $3); }
       ;

expr: expr '+' expr
        {
				int type;
				Node *leaf = $1;
				while(leaf->nb_children > 0)
					leaf = child(leaf,0);
				if(leaf->type == idf_node) {
					entry_t* entry = lookup(symbol_table, leaf->lexeme);
					if(entry){
						type = (entry->type == int_node) ? 0 : 1;
					}
				} else
					type = (leaf->type == int_node) ? 0 : 1;
            Attributes* att = malloc(sizeof(Attributes));
            att->local      = new_local_name();
            att->code       = create_tac();
				if(type == 0)
            att->code->inst = create_inst_tac(att->local,$1->att->local,"ADD",$3->att->local);
				else
            att->code->inst = create_inst_tac(att->local,$1->att->local,"FADD",$3->att->local);
            cat_tac(&$1->att->code,&$3->att->code);
            cat_tac(&$1->att->code,&att->code);
            att->code = $1->att->code;
            $$ = create_node(0, plus_node, expr_lexeme($1,$3,"+"), att, 2, $1,$3);
        }
    | expr '-' expr
        {
				int type;
				Node *leaf = $1;
				while(leaf->nb_children > 0)
					leaf = child(leaf,0);
				if(leaf->type == idf_node) {
					entry_t* entry = lookup(symbol_table, leaf->lexeme);
					if(entry){
						type = (entry->type == int_node) ? 0 : 1;
					}
				} else
					type = (leaf->type == int_node) ? 0 : 1;
            Attributes* att = malloc(sizeof(Attributes));
            att->local      = new_local_name();
            att->code       = create_tac();
            att->code->inst = create_inst_tac(att->local,$1->att->local,"SUB",$3->att->local);
            cat_tac(&$1->att->code,&$3->att->code);
            cat_tac(&$1->att->code,&att->code);
            att->code = $1->att->code;
            $$ = create_node(0, minus_node, expr_lexeme($1,$3,"-"), att, 2, $1,$3);
        }
    | expr '*' expr
        {
				int type;
				Node *leaf = $1;
				while(leaf->nb_children > 0)
					leaf = child(leaf,0);
				if(leaf->type == idf_node) {
					entry_t* entry = lookup(symbol_table, leaf->lexeme);
					if(entry){
						type = (entry->type == int_node) ? 0 : 1;
					}
				} else
					type = (leaf->type == int_node) ? 0 : 1;
            Attributes* att = malloc(sizeof(Attributes));
            att->local      = new_local_name();
            att->code       = create_tac();
            att->code->inst = create_inst_tac(att->local,$1->att->local,"MUL",$3->att->local);
            cat_tac(&$1->att->code,&$3->att->code);
            cat_tac(&$1->att->code,&att->code);
            att->code = $1->att->code;
            $$ = create_node(0, mult_node, expr_lexeme($1,$3,"*"), att, 2, $1,$3);
        }
    | expr '/' expr
        {
				int type;
				Node *leaf = $1;
				while(leaf->nb_children > 0)
					leaf = child(leaf,0);
				if(leaf->type == idf_node) {
					entry_t* entry = lookup(symbol_table, leaf->lexeme);
					if(entry){
						type = (entry->type == int_node) ? 0 : 1;
					}
				} else
					type = (leaf->type == int_node) ? 0 : 1;
            Attributes* att = malloc(sizeof(Attributes));
            att->local      = new_local_name();
            att->code       = create_tac();
            att->code->inst = create_inst_tac(att->local,$1->att->local,"DIV",$3->att->local);
            cat_tac(&$1->att->code,&$3->att->code);
            cat_tac(&$1->att->code,&att->code);
            att->code = $1->att->code;
            $$ = create_node(0, div_node, expr_lexeme($1,$3,"/"), att, 2, $1,$3);
        }
    | '(' expr ')'  { $$ = $2; }
    | INT_LIT
        {
            Attributes* attr = malloc(sizeof(Attributes));
            attr->local      = new_local_name();
            attr->code       = create_tac();
            attr->code->inst = create_inst_tac(attr->local,$1,"","");
            $$ = create_leaf(0, int_node, $1, attr);
        }
    | F_LIT
        {
            Attributes* attr = malloc(sizeof(Attributes));
            attr->local      = new_local_name();
            attr->code       = create_tac();
            attr->code->inst = create_inst_tac(attr->local,$1,"","");
            $$ = create_leaf(0, float_node, $1, attr);
         }
    | lvalue        { $$ = $1; }
    | chamaproc     { $$ = $1; }
    ;

chamaproc: IDF '(' listaexpr ')' { $$ = create_node(0, proc_node, $1, NULL, 1, $3); }
    ;

enunciado: expr         { $$ = $1 ;}
         | IF '(' expbool ')' THEN acoes fiminstcontrole
                {
                    struct tac *t;
                    Attributes *att = create_attr();

                    $$ = create_node(0, if_node, "if", att, 3, $3,$6,$7);
                }

         | WHILE '(' expbool ')' '{' acoes '}'
                {
                    $$ = create_node(0, while_node, "while", NULL, 2, $3,$6);
                    $$->att = create_attr();
                }
         ;

fiminstcontrole: END
                    { $$ = create_leaf(0, end_node, "end", NULL); }
               | ELSE acoes END
                    {
                        $$ = $2;
                        $$->type = else_node;
                        $$->att = create_attr();
                    }
               ;

expbool: TRUE
            {
                $$ = create_leaf(0, true_node, "true", NULL);
                $$->att = create_attr();
            }
       | FALSE
            {
                $$ = create_leaf(0, false_node, "false", NULL);
                $$->att = create_attr();
            }
       | '(' expbool ')'
            { $$ = $2; }
       | expbool AND expbool
            {
                $$ = create_node(0,and_node, expr_lexeme($1, $3, "&&" ), NULL, 2, $1, $3);
                $$->att = create_attr();
            }
       | expbool OR expbool
            {
                $$ = create_node(0,or_node, expr_lexeme($1, $3, "||" ), NULL, 2, $1, $3);
                $$->att = create_attr();
            }
       | NOT expbool
            {
                $$ = create_node(0,not_node, "not", NULL, 1, $2);
                $$->att = create_attr();
             }
       | expr '>' expr
            {
                $$ = create_node(0,op_node, ">" , create_attr(), 2, $1, $3);
             }
       | expr '<' expr
            {
                $$ = create_node(0,op_node, "<" , create_attr(), 2, $1, $3);
            }
       | expr LE expr
            {
                $$ = create_node(0,op_node, "<=" , create_attr(), 2, $1, $3);
            }
       | expr GE expr
            {
                $$ = create_node(0,op_node, ">=", create_attr(), 2, $1, $3);
            }
       | expr EQ expr
            {
                $$ = create_node(0,op_node, "=" , create_attr(), 2, $1, $3);
            }
       | expr NE expr
            {
                $$ = create_node(0,op_node, "!=" , create_attr(), 2, $1, $3);
            }
       ;

%%
 /* A partir daqui, insere-se qlqer codigo C necessario.
  */

char* progname;
int lineno;
extern FILE* yyin;

int main(int argc, char* argv[])
{
   int do_i_print_tree = 0;
   int do_i_print_tac = 1;
	FILE* outfile = NULL;

   if (argc != 2 && argc < 3) {
     printf("uso: %s <input_file> [-t] [-c] [-o] [output tac]. Try again!\nNote that the -t parameter is opitional.\n", argv[0]);
     exit(-1);
   }
   yyin = fopen(argv[1], "r");
   if (!yyin) {
     printf("Uso: %s <input_file>. Could not find %s. Try again!\n",
         argv[0], argv[1]);
     exit(-1);
   }
	int i;
   if(argc > 2)
   {
		for(i = 2; i < argc; i++) {
		   if(strcmp(argv[i], "-t") == 0)
		      do_i_print_tree = 1;
		   else if(strcmp(argv[i], "-c") == 0)
		      do_i_print_tac = 1;
			else if(strcmp(argv[i], "-o") == 0) {
				if(i+1 >= argc) {
					printf("Missing parameter for -o\n");
					exit(-1);
				}
				outfile = fopen(argv[++i], "w");
			}
		   else
		   {
		      printf("Parametro inválido.\n");
		      exit(-1);
		   }
		}
   }

   progname = argv[0];

   srand(0);

   init_table(&symbol_table);

   if (!yyparse ())
      printf("OKAY.\n");
   else
      printf("ERROR.\n");

   if (do_i_print_tree && syntax_tree)
      print_tree(syntax_tree, 0);


   if (do_i_print_tac && syntax_tree && syntax_tree->att && syntax_tree->att->code && outfile)
      print_tac(outfile,syntax_tree->att->code, symbol_table.offset, offset);
	else
		print_tac(stdout,syntax_tree->att->code, symbol_table.offset, offset);

	//print_table(symbol_table);

   return(0);
}

int yyerror(char* s) {
  fprintf(stderr, "%s: %s", progname, s);
  fprintf(stderr, "line %d\n", line_number);
  return 1;
}
